﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Godot.core.os;
using Godot.core.math;

namespace Godot.core.io
{
    static class Marshalls
    {
        [StructLayout(LayoutKind.Explicit)]
        public struct MarshallFloat 
        {
	        [FieldOffset(0)] public uint i; ///< int
	        [FieldOffset(0)] public float f; ///< float
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct MarshallDouble 
        {
	        [FieldOffset(0)] public UInt64 l; ///< long long
	        [FieldOffset(0)] public double d; ///< double
        };

        #region ____encode_functions____
        public static unsafe uint encode_uint32(uint p_uint, byte* p_arr) 
        {
	        for (int i = 0; i < 4; i++) 
            {
		        *p_arr = (byte)(p_uint & 0xFF);
		        p_arr++; 
                p_uint >>= 8;
	        }
	        return sizeof( uint );
        }

        public static unsafe Error encode_variant(Variant p_variant, byte *r_buffer, ref int r_len) 
        {
            return Error.OK;
        }
        #endregion

        #region ____decode_functions____
        
        public static unsafe uint decode_uint32(byte* p_arr) 
        {
	        uint u = 0;	
	        for (int i = 0; i < 4; i++) 
            {
		        uint b = *p_arr;
		        b <<= (i * 8);
		        u |=b;
		        p_arr++;
	        }
	        return u;
        }

        public static unsafe float decode_float(byte* p_arr) 
        {
            MarshallFloat mf = new MarshallFloat();
	        mf.i = Marshalls.decode_uint32(p_arr);
	        return mf.f;
        }

        public static unsafe Error decode_variant(ref Variant r_variant, byte* p_buffer, uint p_len, ref uint r_len)
        {
            byte* buf = p_buffer;
	        uint len = p_len;

	        if(len < 4)
                return Error.ERR_INVALID_DATA;

	        Variant.Type type = (Variant.Type)decode_uint32(buf);

	        if(!Enum.IsDefined(typeof(Variant.Type), type) || type.Equals(Variant.Type.VARIANT_MAX))
                return Error.ERR_INVALID_DATA;

	        buf += 4;
	        len -= 4;
	        
            if (r_len != null)
		        r_len = 4;

	        switch(type) 
            {
                //basic types
                case Variant.Type.NIL: {
			        r_variant = new Variant();
		        } break;

                case Variant.Type.BOOL: {
			        if(len < 4)
                        return Error.ERR_INVALID_DATA;
			        
                    bool val = decode_uint32(buf).Equals(0);
			        r_variant = val;
			        if (r_len != null)
				        r_len += 4;
		        } break;

		        case Variant.Type.INT: {
			        if(len < 4)
                        return Error.ERR_INVALID_DATA;
			        int val = (int)decode_uint32(buf);
			        r_variant=val;
			        if (r_len != null)
				        r_len += 4;
		        } break;

                case Variant.Type.REAL: {
			        if(len < (int)4)
                        return Error.ERR_INVALID_DATA;

			        float val = decode_float(buf);
			        r_variant = val;
			        if (r_len != null)
				        r_len += 4;

		        } break;

		        case Variant.Type.STRING: {
			        if(len < 4)
                        return Error.ERR_INVALID_DATA;
			        
                    uint strlen = decode_uint32(buf);
			        buf += 4;
			        len -= 4;
			        
                    if((int)strlen > len)
                        return Error.ERR_INVALID_DATA;
			        
                    string str = new string((sbyte*)buf, 0, (int)strlen, Encoding.UTF8);
			        r_variant = str;

			        if (r_len != null) 
                    {				
				        if ((strlen % 4) != 0)
					        r_len += 4 - (strlen % 4);
				        r_len += 4 + strlen;
			        }
		        } break;

                case Variant.Type.VECTOR2: {

			        if(len < (int)4 * 2) return Error.ERR_INVALID_DATA;
			        Vector2 val = new Vector2();
			        val.x = decode_float(&buf[0]);
			        val.y = decode_float(&buf[4]);
			        r_variant = val;
			        r_len += 4 * 2;
		        } break;		// 5

                case Variant.Type.VECTOR3: {
			        if(len <(int)4*3) return Error.ERR_INVALID_DATA;
			        Vector3 val = new Vector3();
			        val.x = decode_float(&buf[0]);
			        val.y = decode_float(&buf[4]);
			        val.z = decode_float(&buf[8]);
			        r_variant = val;
                    r_len += 4*3;
		        } break;

                case Variant.Type.PLANE: {
			        if(len < (int)4*4) return Error.ERR_INVALID_DATA;
			        Plane val = new Plane();
			        val.normal.x = decode_float(&buf[0]);
			        val.normal.y = decode_float(&buf[4]);
			        val.normal.z = decode_float(&buf[8]);
			        val.d = decode_float(&buf[12]);
			        r_variant = val;
                    r_len += 4*4;
		        } break;

                case Variant.Type.QUAT: {
			        if(len < (int)4*4) return Error.ERR_INVALID_DATA;
			        Quat val = new Quat();
			        val.x = decode_float(&buf[0]);
			        val.y = decode_float(&buf[4]);
			        val.z = decode_float(&buf[8]);
			        val.w = decode_float(&buf[12]);
			        r_variant = val;
                    r_len += 4*4;
		        } break;

                case Variant.Type.AABB: {
			        if(len < (int)4*6) return Error.ERR_INVALID_DATA;
			        Aabb val = new Aabb();
			        val.pos.x = decode_float(&buf[0]);
			        val.pos.y = decode_float(&buf[4]);
			        val.pos.z = decode_float(&buf[8]);
			        val.size.x = decode_float(&buf[12]);
			        val.size.y = decode_float(&buf[16]);
			        val.size.z = decode_float(&buf[20]);
			        r_variant = val;
                    r_len += 4*6;
		        } break;

                case Variant.Type.RECT2: {
			        if(len < (int)4 * 4) return Error.ERR_INVALID_DATA;
			        Rect2 val = new Rect2();
			        val.pos.x = decode_float(&buf[0]);
			        val.pos.y = decode_float(&buf[4]);
			        val.size.x = decode_float(&buf[8]);
			        val.size.y = decode_float(&buf[12]);
			        r_variant = val;
			        r_len += 4*4;

		        } break;

                case Variant.Type.MATRIX3: {
			        if(len < (int)4*9) return Error.ERR_INVALID_DATA;
			        Matrix3 val = new Matrix3();
			        for(int i = 0; i < 3; i++) 
                    {
				        for(int j = 0; j < 3; j++) 
                        {
					        val.elements[i][j]=decode_float(&buf[(i*3+j)*4]);
				        }
			        }

			        r_variant = val;
			        r_len += 4*9;
		        } break;
		
                case Variant.Type.TRANSFORM: {
			        if(len < (int)4*12) return Error.ERR_INVALID_DATA;
			        Transform val = new Transform();
			        for(int i = 0; i < 3; i++) 
                    {
				        for(int j = 0; j < 3; j++) 
                        {
					        val.basis.elements[i][j] = decode_float(&buf[(i*3+j)*4]);
				        }
			        }
			        val.origin[0] = decode_float(&buf[36]);
			        val.origin[1] = decode_float(&buf[40]);
			        val.origin[2] = decode_float(&buf[44]);
			        r_variant = val;
                    r_len += 4*12;
		        } break;

                case Variant.Type.IMAGE: {
			        if(len < (int)5*4) return Error.ERR_INVALID_DATA;
			        
                    Image.Format fmt = (Image.Format)decode_uint32(&buf[0]);

			        if( fmt >= Image.Format.FORMAT_MAX) return Error.ERR_INVALID_DATA;
			        
                    uint mipmaps = decode_uint32(&buf[4]);
			        uint w = decode_uint32(&buf[8]);
			        uint h = decode_uint32(&buf[12]);
			        uint datalen = decode_uint32(&buf[16]);

			        Image img = null;
			        if (datalen > 0) 
                    {
				        len -= 5*4;
				        if(len < datalen) return  Error.ERR_INVALID_DATA;
				        byte[] data = new byte[datalen];
     
                        IntPtr ptr = new IntPtr(&buf[20]);
                        Marshal.Copy(ptr, data, 0, (int)datalen);

				        img = new Image((int)w, (int)h, (int)mipmaps, fmt, data);
			        }

			        r_variant = img;
			        r_len += 4*5 + datalen;

		        } break;

                case Variant.Type.COLOR: {
			        if(len < (int)4*4) return Error.ERR_INVALID_DATA;
			        Color val = new Color();
			        val.r = decode_float(&buf[0]);
			        val.g = decode_float(&buf[4]);
			        val.b = decode_float(&buf[8]);
			        val.a = decode_float(&buf[12]);
			        r_variant = val;
                    r_len += 4*4;
		        } break;

                case Variant.Type.NODE_PATH: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint strlen = decode_uint32(buf);
			        buf += 4;
			        len -= 4;
			        if((int)strlen > len) return Error.ERR_INVALID_DATA;
			        string str = new string((sbyte*)buf,0, (int)strlen, Encoding.UTF8);
			        r_variant = new NodePath(str); 
                    r_len += 4 + strlen;
		        } break;

                case Variant.Type.INPUT_EVENT: {

			        InputEvent ie = new InputEvent();

			        ie.type = (InputEvent.Type)decode_uint32(&buf[0]);
			        ie.device = (int)decode_uint32(&buf[4]);
			        len = decode_uint32(&buf[8]) - 12;

			        r_len += 12;

			        switch(ie.type) {

				        case InputEvent.Type.KEY: {

					        uint mods = decode_uint32(&buf[12]);
					        if ((mods & (uint)KeyModifierMask.KEY_MASK_SHIFT) != 0)
						        ie.key.mod.shift = true;
					        if ((mods & (uint)KeyModifierMask.KEY_MASK_CTRL) != 0)
						        ie.key.mod.control = true;
					        if ((mods & (uint)KeyModifierMask.KEY_MASK_ALT) != 0)
						        ie.key.mod.alt = true;
					        if ((mods & (uint)KeyModifierMask.KEY_MASK_META) != 0)
						        ie.key.mod.meta = true;
					        ie.key.scancode = decode_uint32(&buf[16]);

					        r_len += 8;
				        } break;

				        case InputEvent.Type.MOUSE_BUTTON: {
					        ie.mouse_button.button_index = (int)decode_uint32(&buf[12]);
					        r_len += 4;
				        } break;

				        case InputEvent.Type.JOYSTICK_BUTTON: {
					        ie.joy_button.button_index = (int)decode_uint32(&buf[12]);
					        r_len += 4;
				        } break;

				        case InputEvent.Type.SCREEN_TOUCH: {
					        ie.screen_touch.index = (int)decode_uint32(&buf[12]);
					        r_len += 4;
				        } break;

				        case InputEvent.Type.JOYSTICK_MOTION: {
					        ie.joy_motion.axis = (int)decode_uint32(&buf[12]);
					        r_len += 4;
				        } break;
			        }
			        r_variant = ie;

		        } break;

                case Variant.Type.DICTIONARY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
                    uint count = decode_uint32(buf);
                    bool shared = (count & 0x80000000) != 0;
                    count &= 0x7FFFFFFF;

			        buf += 4;
			        len -= 4;

			        r_len += 4;

                    Dictionary<Variant,Variant> d = new Dictionary<Variant,Variant>();

                    for(uint i = 0; i < count; i++) 
                    {
                        Variant key = new Variant();
                        Variant value = new Variant();

				        uint used = 0;
				        Error err = decode_variant(ref key, buf,(uint)len, ref used);
				        if(err != Error.OK) return err;

				        buf += used;
				        len -= used;
				        r_len += used;				        

				        err = decode_variant(ref value, buf, (uint)len, ref used);
				        if(err != Error.OK) return err;

				        buf += used;
				        len -= used;
				        r_len += used;
				        
				        d[key] = value;
			        }

			        r_variant = d;

		        } break;

                case Variant.Type.ARRAY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint count = decode_uint32(buf);
                    bool shared = (count & 0x80000000) != 0;
                    count &= 0x7FFFFFFF;

			        buf += 4;
			        len -= 4;
			        
                    r_len +=4;
			        
                    List<Variant> varr = new List<Variant>();

                    for(uint i = 0; i < count; i++) 
                    {
				        uint used = 0;
				        Variant v = new Variant();
				        Error err = decode_variant(ref v, buf,(uint)len,ref used);
				        if(err != Error.OK) return err;
				        buf += used;
				        len -= used;
				        varr.Add(v);
				        r_len += used;				       
			        }

			        r_variant = varr;
                } break;

                // arrays
		        case Variant.Type.RAW_ARRAY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint count = decode_uint32(buf);
			        buf += 4;
			        len -= 4;
			        if((int)count > len) return Error.ERR_INVALID_DATA;

			        byte[] data = null ;

			        if (count > 0) 
                    {
				        data = new byte[count];				        
				        for(int i = 0 ; i < count; i++) {
					        data[i] = buf[i];
				        }				        
			        }

			        r_variant = new List<byte>(data);
			        
				    if (count % 4 != 0)
					    r_len += 4 - count % 4;
				    r_len += 4 + count;
		        } break;

                case Variant.Type.INT_ARRAY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint count = decode_uint32(buf);
			        buf += 4;
			        len -= 4;
			        if((int)count * 4 > len) return Error.ERR_INVALID_DATA;

			        int[] data = null;

			        if (count > 0) 
                    {
				        //const int*rbuf=(const int*)buf;
				        data = new int[count];
				        for(int i = 0;i < count; i++) 
                        {
					        data[i]= (int)decode_uint32(&buf[i*4]);
				        }
			        }
			        r_variant = new List<int>(data);
			        r_len += 4+count * sizeof(int);			        
		        } break;

                case Variant.Type.REAL_ARRAY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint count = decode_uint32(buf);
			        buf += 4;
			        len -= 4;
			        if((int)count * 4 > len) return Error.ERR_INVALID_DATA;

			        float[] data = null;

			        if (count > 0) 
                    {
				        //const float*rbuf=(const float*)buf;
				        data = new float[count];				       
				        for(int i = 0; i < count; i++) 
                        {
					        data[i] = decode_float(&buf[i*4]);
				        }				     
			        }
			        r_variant = new List<float>(data);
			        r_len += 4 + count * sizeof(float);			        
		        } break;

                case Variant.Type.STRING_ARRAY: {
                    if(len < 4) return Error.ERR_INVALID_DATA;
			        
                    uint count = decode_uint32(buf);
			        
                    if(count < 0) return Error.ERR_INVALID_DATA;

			        List<string> strings = new List<string>();
			        buf += 4;
			        len -= 4;

			        r_len += 4;
			        //printf("string count: %i\n",count);

			        for(int i = 0; i < (int)count; i++) 
                    {
				        if(len < 4) return Error.ERR_INVALID_DATA;

				        uint strlen = decode_uint32(buf);

				        buf += 4;
				        len -= 4;
				        
                        if((int)strlen > len) return Error.ERR_INVALID_DATA;

				        //printf("loaded string: %s\n",(const char*)buf);
                        string str = new string((sbyte*)buf, 0, (int)strlen, Encoding.UTF8);

				        strings.Add(str);

				        buf += strlen;
				        len -= strlen;

				        r_len += (4 + strlen);

				        if ((strlen % 4) != 0) 
                        {
					        int pad = 4 - ((int)strlen % 4);
					        buf += pad;
					        len -= (uint)pad;
					        r_len += (uint)pad;
				        }
			        }
			        r_variant = strings;
                } break;

                case Variant.Type.VECTOR3_ARRAY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint count = decode_uint32(buf);

			        if(count < 0) return Error.ERR_INVALID_DATA;
			        buf += 4;
			        len -= 4;

			        if((int)count * 4 * 3 > len) return Error.ERR_INVALID_DATA;
			        
                    Vector3[] varray = null;

			        r_len += 4;
			        
			        if (count > 0) 
                    {
				        varray = new Vector3[count];
				        
				        float *r = (float*)buf;

				        for(int i = 0; i < (int)count; i++) 
                        {
					        varray[i].x = decode_float(buf + i * 4 * 3 + 4 * 0);
                            varray[i].y = decode_float(buf + i * 4 * 3 + 4 * 1);
                            varray[i].z = decode_float(buf + i * 4 * 3 + 4 * 2);
				        }

				        uint adv = 4 * 3 * count;
                        r_len += adv;
				        len -= adv;
				        buf += adv;

			        }

			        r_variant = new List<Vector3>(varray);

		        } break;

                case Variant.Type.COLOR_ARRAY: {
			        if(len < 4) return Error.ERR_INVALID_DATA;
			        uint count = decode_uint32(buf);
			        if(count < 0) return Error.ERR_INVALID_DATA;
			        buf += 4;
			        len -= 4;

			        if((int)count * 4 * 4 > len) return Error.ERR_INVALID_DATA;
			        Color[] carray = null;

			        r_len += 4;
			        
			        if (count > 0) 
                    {
				        carray = new Color[count];
				       
				        float *r = (float*)buf;

				        for(int i = 0; i < (int)count; i++) 
                        {
                            carray[i].r = decode_float(buf + i * 4 * 4 + 4 * 0);
                            carray[i].g = decode_float(buf + i * 4 * 4 + 4 * 1);
                            carray[i].b = decode_float(buf + i * 4 * 4 + 4 * 2);
                            carray[i].a = decode_float(buf + i * 4 * 4 + 4 * 3);

				        }
				        uint adv = 4 * 4 * count;
				        r_len += adv;
				        len -= adv;
				        buf += adv;

			        }

			        r_variant = new List<Color>(carray);

		        } break;

                //math types
                default: return Error.ERR_BUG;
            }
            return Error.OK;
        }

        #endregion
    }
}
